Draw a circle around nodes groups
Asked Answered
V

2

4

In the following vis-network I have 2 groups of nodes. I have separated the 2 groups of nodes into left and right side by accessing the node positions after generating a layput_as_tree. Now would like to draw a circle or ellipse around the node groups. here is a reproducible example

require(shiny)
require(visNetwork)
server <- function(input, output) {
  output$network <- visNetwork::renderVisNetwork({
    edges <- data.frame(
      from = sample(1:10, 8),
      to = sample(1:10, 8),
      label = paste("interaction type", 1:8),
      length = c(100, 500),
      width = c(4, 1),
      arrows = c("to", "from", "middle", "middle;to"),
      dashes = c(TRUE, FALSE),
      title = paste("interaction name", 1:8),
      smooth = c(FALSE, TRUE),
      shadow = c(FALSE, TRUE, FALSE, TRUE)
    )
    nodes <- data.frame(
      id = 1:10,
      group = c("A", "B"),
      label = paste("Node", 1:10),
      shape = "ellipse"
    )

    # save the graph in variable
    g <-
      visNetwork::visNetwork(nodes, edges, height = "500px", width = "100%") %>% 
      visNetwork::visIgraphLayout(layout = "layout_as_tree")

    # access the x and y co-ordinates to arrange the groups
    coords <- g$x$nodes %>%
      dplyr::mutate(x = abs(x)) %>%
      dplyr::mutate(y = abs(y)) %>%
      dplyr::mutate(x = ifelse(group %in% "A", -x, x)) %>%
      dplyr::select(x, y) %>%
      as.matrix()

    #' replot the network with the new co-ordinates
    visNetwork::visNetwork(nodes, edges, height = "500px", width = "100%") %>%
     visNetwork::visIgraphLayout(
      layout = "layout.norm",
      layoutMatrix = coords,
      randomSeed = 1,
      smooth = T
    ) 
  })
}

ui <- shiny::fluidPage(
  visNetwork::visNetworkOutput("network",
    width = "1000px", height = "700px"
  )
)

shiny::shinyApp(ui = ui, server = server)
Verbalism answered 5/1, 2021 at 17:13 Comment(20)
I am not quite sure: what you achieved (last picture) does not look like what you were asking for? What is the problem now?Violate
What do you mean by "programmatically to get the x and y closer to my picture 2"? Some algorithm?Violate
I'd suggest that you have look here https://mcmap.net/q/259662/-graph-auto-layout-algorithm first. Then i would open a new question when it is clear what you need exactly.Violate
There are already different algs that do that - see my link. Why do you need anything else? What does "look good" mean? What is different from existing algs in your case?Violate
From what it sounds like... you may want a sorting algorithm? For instance... get all the nodes (I am assuming they have their group names). If in group a, store the reference in a dictionary / array, and likewise for group b. Then writing something to adjust the position of the nodes is probably trivial. That's more of a design question. Something like this is possible with javascript and html alone.Afterward
So if I understand correctly, I don't have adjust the paths from one node to another, correct? The library takes care of connecting the nodes from one to the other simply by knowing the coordinates, right? I also need to make sure that you can get: 1. any point on the node (so that way we can get the parent container nodes coords), 2. the height and width of any node (for parent nodes), and 3. where the coordinates are "centered" around on the node - ie (x, y) is the top left, bottom right, center, etc. Sorry I didn't reply the 8th, I didn't receive a notification message, @user5249203.Afterward
After looking a bit more at the differences between both graphs, are you hoping to keep paths uncrossed as well?Afterward
If that is the case, I would also need to know how many children each node has.Afterward
@ShanerM13, Yes, You don't need to adjust the path from one node to another node, the library takes care of it. If the paths cross that should be OK. The package avoids overlapping the nodes. The 3 points you raised, I don't know how to get those. I printed the data.frame for x and y.Verbalism
Sorry for the delay. I've been busy with life. Their might be a few other options to do this, however the problem is how it would be displayed from platform to platform. I would probably use the .getBoundingClientRect() method which is accurate like 95% of the time - 5% being in weird css situations or page loading or something like that... anyways... The answer might take me longer to code, due to the fact that I am going to have to create my own classes... or I might just get a little cheatie and show you the idea.Afterward
Oh... You took the output out too :(Afterward
It depends on what "canvas" you are using... If you are refering to the webpage as a "canvas" then you can use the css border-radius property to sheer an element to a circular shape. If you are using the html5 canvas tag, thats a little bit different. And dang it! You beat me to it! I am glad you figured it out though. I was going to make randomly generated points to demonstrate the concept.Afterward
So you drew the circle onto the canvas, good! :) . If your input (the circle) erases everything on the canvas the problem is probably because you are creating a new canvas object from a <canvas> tag that already had a canvas object. Sounds confusing. However, I am uncertain as to how browsers actually handle creating new canvas objects for the same <canvas> tag. I don't know if it layers it, if it erases it, or what. I don't generally mess with the <canvas> tag to begin with, but I can. I will read up to confirm what I said is right.Afterward
So... Trivial was definitely the wrong word. It is more involved than that. It should still be doable... Interesting. You should be able to check that within the browser (id's) simply by opening up the interactive developer tools ctrl+shift+i or variations of it will do that.Afterward
Revision of the comment above: the reason why this is proving to be more difficult for me is because I am generating a network of heavily interconnected nodes, versus a network that you provided where at most 1 node references 2 nodes, thus making a loop... Mine doesn't do that... I am trying to be clever and creative to fix my problem, which will definitely fix your problem...Afterward
I think the task that I have made is near impossible, however if you are only expecting 2 nodes to be connected to each other at all times max, I think I can do it. I just want a confirmation as to if that is what you are expecting and I will give it a go. The network that was being generated is just far too complicated.Afterward
Which nodes should be located at the edge? Please provide clear rules. And perhaps you should really start a new question. Nobody wants to read 100 comments ;-)Violate
@Christoph, for demonstration may be Node 1, 3, 4 ,6, 8, 10. Should I delete and re post? Form the question I posted in the beginning, I have been slowly solving the steps. Hence, have been updating the same question. The last step I am left is to arrange the nodes as I need.Verbalism
Dont't delete the questions, just start a new one. To your example: if want to apply an algorithm in the future, the rule `nodes_at_edges <- c(1, 3, 4, 6, 8, 10)? Does that make sense?Violate
@Violate added a new question here. Sorry, it did not make sense, how would that arrange the positions without adding the x and y ?Verbalism
V
1

Using the visEvents and passing a Javascript code was able to generate the circle around the node groups.

graph %>%
    visNetwork::visEvents(type = "on", beforeDrawing = "function(ctx) {
    ctx.fillStyle = 'rgba(255, 0, 255, 0.1)';
    ctx.ellipse(-180 , 25, 150, 280 , 0, 0, 2 * Math.PI);
    ctx.fill();
    ctx.fillStyle = 'rgba(64, 255, 255,0.1)';
    ctx.ellipse(180 , 25, 150, 280, 0, 0, 2 * Math.PI);
    ctx.fill();
}")

enter image description here

Verbalism answered 1/2, 2021 at 8:59 Comment(0)
A
1

I am in the middle of finishing the script, but I have to leave... will be back to finish.

//The nodeGraph variable should probably be ran through a transitive
//closure algorithm to simiplify it, so its not an overly complicated
//network
nodeGraph = {};


//create nodes and the beginnings of a dictionary for a directed graph
//to later be used to adjust the positions of nodes - note this is not
//the most efficient algorithm.

nodeCount = 10;

for (var i = 0; i < nodeCount; i++)
{
  var div = document.createElement('div');
  div.id = "node" + i;
  div.className = "node";
  div.setAttribute("group", (randomInt(1, 2) == 1) ? "A" : "B")
  nodeGraph["node" + i] = [];
  document.getElementsByClassName('container')[0].append(div);
}

//here I randomly create a relationship amongst nodes - but I limit it to 5 relationships just so its not too resource heavy.

//loop through each node
for (var i = 0; i < nodeCount; i++)
{
  //generate number of relationships
  randInt = randomInt(1, 5);
  
  //generate random relationships
  for (var j = 0; j < randInt; j++)
  {
    ranNum = randomInt(0, nodeCount - 1);
    //console.log(ranNum);
    while (nodeGraph["node" + i].includes(ranNum))
    {
      ranNum = randomInt(0, nodeCount - 1);
    }
    //console.log(ranNum);
    nodeGraph["node" + i].push("node" + ranNum);
  }
}

//outputs the random relationship amongst nodes
console.log(nodeGraph);

//the above code sets up the problem for what we want to achieve
//which is to essentially sort the nodes into the two "cells"

//lets get the location of the parent cells and a reference to them
groupABox = document.getElementById('GroupA');
groupABBox = groupABox.getBoundingClientRect();
groupBBox = document.getElementById('GroupB');
groupBBBox = groupBBox.getBoundingClientRect();
//then loop through every node and stick them into their respective groups
for (var i = 0; i < nodeCount; i++)
{
  currentNode = document.getElementById("node" + i);
  group = currentNode.getAttribute('group');
  if (group == 'A')
  {
    relationships = nodeGraph['node' + i];
    for (var j = 0; j < relationships.length; j++)
    {
      comparedNode = document.getElementById(relationships[j]);
      if (comparedNode.getAttribute('group') == 'A')
      {
      }
      else
      {
      }
    }
  }
}

function randomInt(min, max)
{ 
  return Math.floor(Math.random() * (max - min + 1) + min);
}
.parentNode
{
  border-radius: 100px;
  border: solid black 5px;
  height: 500px;
  width: 200px;
  position: relative;
  background-color: lightblue;
}

#GroupA
{
  float: left;
}

#GroupB
{
  float: right;
}

.node
{
  height: 20px;
  width: 20px;
  position: absolute;
  float: none;
  background-color: green;
}
<div class="container">
  <div id="GroupA" class="parentNode">
  </div>
  <div id="GroupB" class="parentNode">
  </div>
</div>

https://jsfiddle.net/Shmac/x1wf52ba/1/

Afterward answered 24/1, 2021 at 23:1 Comment(4)
I solved the circle problem and updated the Q. Would you mind to delete the old comments as our discussion was about the circle and so that, that would not confuse readers. Thank youVerbalism
Yes. I was trying to mention that the circle problem couldn't be solved with sheer javascript due to the fact that to draw on the canvas in the first place requires an object to be created - specifically, a context. Something like that should be native to shiny. When you are trying to draw a circle after the points have been created, the original context gets replaced with the new one created. Should I remove my answer as well? I don't foresee it being useful, unfortunately. I'd have to put it much work to generate nodes that are only connected to 2 max - and then create the algorithm...Afterward
Sure, I was not aware the VisEvents is where we can pass the JS code. Once I understood that, all I did was check the w3 schools on drawing a circle on canvas, because the visnetwork is displayed on the canvas and that lead to the solution.Verbalism
Glad you got it figured out. Sorry my answer was so useless, lol.Afterward
V
1

Using the visEvents and passing a Javascript code was able to generate the circle around the node groups.

graph %>%
    visNetwork::visEvents(type = "on", beforeDrawing = "function(ctx) {
    ctx.fillStyle = 'rgba(255, 0, 255, 0.1)';
    ctx.ellipse(-180 , 25, 150, 280 , 0, 0, 2 * Math.PI);
    ctx.fill();
    ctx.fillStyle = 'rgba(64, 255, 255,0.1)';
    ctx.ellipse(180 , 25, 150, 280, 0, 0, 2 * Math.PI);
    ctx.fill();
}")

enter image description here

Verbalism answered 1/2, 2021 at 8:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.